查看原文
其他

【第1783期】蚂蚁前端研发最佳实践

云谦 前端早读课 2019-11-22

前言

在成都全栈大会上分享的文字版。今日早读文章由蚂蚁金服@云谦授权分享。

正文从这开始~~

准备这个题目时我 google 了下前端最佳实践,排在前面的是讲前端代码规范,语意、可读性、编码规范、空格还是 Tab 等等,我觉得这是我们第一代的最佳实践。

而现在都 9012 年了,最佳实践也经历了很多代的变更,下面是我们在这方面的思考和实践。

自我介绍


目录


为什么要有最佳实践?

不知大家在这些方面是否有疑惑?

前端发展快,每次发布 Umi 版本时,除了点赞,还有人求别发,表示实在学不动了。右边这张图是之前的朋友圈看到的,转到公司群里,有共鸣的人不少,说明一定程度反应了现在前端社区的情况。

面对海量“轮子”,我应该学哪些,不学哪个?,我的前端知识点学习列表已经是完全学不完的状态,比如社区上光数据流方案就有几百个,其中值得一看的也有四五十个吧。

然后,大家在创建项目时,是否也有过选择困难?


这里举一个具体的例子,

  • 框架

  • 基于 React 的框架

  • 语言

  • CSS 方案

  • 数据流

  • 请求库

  • 等等

可以发现,每个点都会有不少选择,并且有时还真的很难选,因为每个人看待它的角度不同。所以,对于开发者来说,真的有点太难了!他只是想完成需求,然后回家睡觉,为啥需要选这么多,就不能给个默认的吗?

然后,

如果每个项目都要选,我觉得会很难。当然,也有人可能会觉得是一种乐趣。

而对于团队而言,如果每个项目的选择还都不一样,那么团队的研发成本和效率都会是问题。想象一下,如果一个同学换个项目组,需要接触完全不同的技术栈。

所以,对于团队而言,保持一致非常重要。

那么,如何保持一致?不同团队会有不同的选择,通常有这几类,

  • 文档

  • 脚手架

  • 框架

约束能力和迭代能力也是逐步递增。

我们最早应该是用的文档。比如做一些代码约定,用 Tab 还是空格,用两个空格还是 4 个空格,行尾要不要加分号等等,这一类主要靠开发者自觉,所以我觉得不太靠谱,这也是为啥后来有 eslint。

脚手架比文档好点,但也依赖开发者的自觉性,因为还是可以随便改。前几年 React 社区上有不少做的很好的脚手架,但现在基本上都没有活跃的了。

第三种方式是框架,他的约束性可以做的比较强。比如约定用 less,如果开发者用了 sass,就给他报个错。同时相比其他两种方式,还有迭代能力。脚手架交给用户之后是很难更新的,框架则是自己更新后,开发者的项目自动生效。

当然,这三者不是互斥关系,可以都用嘛。

然后如何决定用啥方案,用 SASS 还是 LESS,要不要用 TypeScript,甚至目录用复数还是单数这种极其无聊的事情。

不同团队会有不同的选择,

  • STAR 数,大家通常会选 STAR 数多的,社区认同感很重要,比如 DVA 在蚂蚁的推广就是先从社区做起的

  • 简单 vs. 规范,有人会选择概念少而简单的,有人会选择概念多但看起来更规范的

  • 先入为主,先占坑的往往具有优势

  • 老板喜欢,😆

老板喜欢其实 “很重要”。有些大家吵很久但决定不了的事,往往会很自觉地找老板或者德高望重的同学进行拍板,我们也是如此。

蚂蚁前端的选择

我们在不同时期的最佳实践是不同的,曾经还开发过 spm,不自量力地试图挑战 npm + webpack 组合,虽然失败了,但敢想也是一种勇气。(做 spm 时,webpack 还没出来)

我们有很多方向,然后每个方向又有很多选择,图上是我们目前的选择。

从这里可以看到几点,

  • 选择的内容基本上是社区主流的,不脱离社区是基本原则

  • 很多子方向都选择了自研或者正在考虑自研

为啥要自研呢?

我觉得自研会带来一些好处,

  • 自主权 vs. 成本,在拥有自主权的时候,需要评估其带来的成本,以及潜在的弃坑可能

  • 定制化,

  • 需求满足,社区方案有时并不能很好地契合我们的需求,尤其当我们很深入地去使用的时候

  • 售后服务,出错是能找到 owner 的同学是非常重要的一点

有些开源库看起来美好,但真正用下来会发现坑不少。比如组件的文档工具,目前是选择的 docz 和 storybook,但两者用地都有些说不出来的不舒服,并且和 umi 是两个生态的东西,所以我们正考虑基于 umi 开发自己的文档工具,可能叫 umipress 或者 father-doc 。

沉淀的方式是以框架为主,文档、脚手架、资产市场为辅。

  • 框架具有更强大的约束性和迭代能力,这也是我们所需要的

  • 对外是 Umi,面向社区

  • 对内是 Bigfish,在 Umi 的基础上解决流程和业务问题

插件和插件

我们把使用到的技术都沉淀到框架(Bigfish)里。框架像是一个魔法球,把各种技术栈吸到一起,加工后吐给用户,以此来支撑业务。

对于用户来说,Bigfish 框架是唯一依赖。唯一依赖会带来一些实际的好处,这也是我们一直在内部坚持这一点的原因,

  • 技术收敛,保持团队开发模式的一致性

  • 无痛升级,我们既要保持对社区的技术跟进,又要让业务项目跟上步伐,这些中间的屎只能让框架吃掉,让开发者尽可能地无痛升级

  • 应用治理,相比散落的遍地开花的依赖,唯一依赖可以让我们更好地推动用户升级框架,因为只要管一个点即可

唯一依赖的问题就是大而全,虽然看起来挺不优雅,但实际用过之后会发现还蛮香的,除了一开始安装他会有点慢。(这一点我们后续会通过启动器解决)

做了技术栈收敛之后,我觉得对外可能够了,但对内还远远不够。

  • 接流程,让开发者能更顺畅地跑通创建、本地开发、联调、部署、发布和统计

  • 接后端框架,后端可能是 Java、Node 或者 PHP(😆),不同后端对于前端产物的要求会不同,在框架里做好对接,开发者就不用费心思了

  • 接场景,场景有很多种,在框架层也需做好对接。举一些例子

    • SPA 应该是目前用地最多的一种应用类型,但有时也会不满足需求

    • 比如运营页面,多个页面之间没有一点点关系,也不需要互相跳转,用 SPA 就没有意义,这时候 MPA 可能更适合

    • 比如语雀,我们的文档平台,他有前台、有后台、有 PC 端、有无线端,如果整体是一个 SPA,不仅尺寸大,公共依赖的提取是个问题,不同场景之间可能还会相互影响,这时候,多 SPA 的组合会更适合他

    • 微前端前面已经提过

    • SSR 和 Prerender 则是为了更好的浏览器性能,顺便解决 SEO 的问题

  • 接服务,比如登录服务,统计服务,问卷服务,评论服务等等

实现方式是一“件”接入,这里的件是插件,一个插件实现一个功能。然后,我们就有了很多插件。

有了插件之后,我们可以筛选一些插件出来形成插件集,以满足某个业务的需求,类似 babel 的 plugin 和 preset,或者 eslint 的 rule 和 config。

这种方式首先可以满足不同业务的需求。比如无线业务,会比较关注性能,所以可能会选一个切 preact 到 react 的插件、极速版补丁插件、高清方案、fastclick 等等,形成一个插件集。

然后还可以满足一个技术的不同实现,在一个业务类型丰富的大团队中,是允许有不同的选择的。比如数据流,大家的选择可能不同,有些用 dva,有些用 hooks,有些用 mobx,有些自研一套;比如补丁方案,有常规版、极限版,还有终极版。

这是 umi 的插件三态,讲过好多次了,文字稿里就不重复了。

这是 umi 插件的示例。想提一点的是,会用 umi 和会写 umi 插件是两个完全不同的状态,会写 umi 插件,你基本可以魔改 umi 内部 70% 的功能,可以此来达到满足需求业务需求的目的。

资产市场和场景市场

先来看下开发者的时间都去哪了。这是我咨询了一些同事拍脑袋整理的,不太准确。

  • 20% 流程相关,从创建到发布和发布后统计

  • 40% 组件使用和开发,如果有合适组件,直接使用;如果没有,花时间开发

  • 30% 交互场景,解决遇到某个交互场景如何处理,以及处理来自后台的请求,把数据和视图串起来

  • 10% 其他

知道了时间分配后,大家应该知道投时间去解决哪部分的问题,才能真正达到提效的目的了吧。

资产市场用于解决 40% 的开发者时间,非常重要。分为四个概念,

  • 基础组件,antd

  • 业务组件,基于 antd 封装的具有业务属性的组件,不对外

  • 区块,组件的使用片段,区块是为了方便地把代码片段加到项目代码中

  • 模板,多个区块组成的页面

而资产市场要真正达到提效的目的,我觉得还需要解决一些关键的点,才能让整个流程跑起来。

  • 资产质量,组件参考 antd,区块和模板是实实在在要被添加到用户项目的代码,我觉得比组件更难,需要形成对什么是好代码的共同认识,谁都不希望自己的项目变脏

  • 打通上下游,包括组件的生产和消费。生产方是设计师和前端,需要保证组件的本地开发、文档、打包、发布等环节;消费方也是设计师和前端,资产市场不仅是给前端用的,设计师也得用,只有前端拿到的设计稿有大量可以对应的资产时,前端开发才能真正提效,所以,设计师是否有能力让资产市场覆盖 50% 甚至 80% 的场景非常重要

这是内部的资产市场和外部开源的 antd。


这是资产市场通过 umi ui 的方式使用,支持区块、模板以及布局区块。


右图来自开源库 Friend-List,这是一个 suggestion 的实现,他可以简单做,也可以复杂做。复杂做的话,细节点就会很多,比如:

  • 每次输入都要做请求

  • 快速输入的时候,要使用最后的请求,并且取消前面的请求

  • 输入需要同步到 url

  • 输入还需要同步到 history,支持前进后退

  • 请求加缓存

  • 请求出错处理

  • ...

而如果每个开发者都要去关心这些细节,会很难,成本也很高。那么如何让开发者做到又快,产品体验又好,我觉得可能需要场景市场,用于解决 30% 的交互场景需求。

沉淀方式可以用 hooks + 文档的方式;覆盖面从最简单的 CURD 开始,到各种复杂场景。

这里是部分的场景举例。

理想的工作流图。

强约束的垂直领域框架

基于前面讲的插件和插件集的方式,我们已经能够满足各种丰富的业务场景,但是仍然给予了用户很多选择,选择包括选择插件,以及 umi 自身的大量配置项。

对于一些垂直领域,其实还可以做到更好,所以我们最近一直在思考“蚂蚁前端应该如何写中台代码”。

有几个关键的思路,

  • 专治,不提供自由的技术栈选择一定程度上会限制开发者的,但是效率高的,就看你要哪个了

  • 极简,不仅仅是简单,还要优雅;不仅要写地少,还要写地好

然后就强约束、配置化和约定化展开聊下。

前面我们已经了解了一致性的重要性,所以何不把这一点做到底呢?

  • 只能用 TypeScript,用 JavaScript 会报错

  • 只能用 less + css modules,用 sass、stylus、css in js 会报错

  • 只能用内置的数据流方案,用 redux、mobx 等等会报错

  • 等等

图上只列了一部分。

这里的有些约束甚至会有些反人类,但我觉得约束越强,越能保持大家的一致性,如果我们已经把这条路探地很清楚了,少给选择或许是更好的选择。有些限制还不确定是不是好的方式,但是第一版会尽量把规则收拢地紧一些。

配置化不仅是框架和插件的配置,还包括 UI 。

右图是 ant-design-pro 的图,其中 LOGO、导航、菜单对于 90% 的每个页面来说都是固定的,变化的只有右下的页面区,所以我们何不把固定的部分做成配置呢?

比如:

  1. exportdefault{

  2. layout: {

  3. logo: string;

  4. title: string;

  5. renderRender: function;

  6. logout: function;

  7. },

  8. routes: [

  9. {

  10. path,

  11. // 菜单配置

  12. menu: { name, icon, showBreadcrumb },

  13. // 权限配置

  14. access,

  15. },

  16. ],

  17. }

Layout 是其中一个例子,还可以有更多 UI 的配置化。这也是在一定程度在像 low code 的模式靠,我觉得某些研究地很透的垂直场景下,low code 能让研发更高效。

所以我们把适合做成配置的全部配置化,而不能配置的,则会走约定化。

之前有用过 ruby on rails 框架,特别喜欢那种约定化的编码方式,所以我们希望把他也搬到前端研发流程里。

  • 建一个 locales 目录,就拥有了国际化

  • 建一个 models 目录,就拥有了数据流

  • 建一个 mock 目录,就拥有了数据 mock

  • 建一个 access.ts 文件,就拥有了权限策略

  • ...

看起来很黑盒,按照我们约定的方式编码,并且只能这样编码,然后他就能 run 起来。

这是之前在朋友圈看到的图,大家体会下,但这就是我们想要实现的样子。

极简数据流是整体方案的其中一环。

右边是之前做数据流调研时做的整理,发现那么多数据流方案基本都是在这些方案上的差异,而要选哪个就看你对哪些方面比较关心。这部分展开聊比较长,之后会额外写一篇文章介绍。

然后我们还调研了下公司内部的中台项目,发现大部分是简单的 CURD,并且全局数据使用较少,比如通知、登录、当前用户信息等。所以,我们可能是需要一个不那么复杂的,用起来又很简单的数据流方案。

最终讨论下来的方案有几个特点,

  • 基于 hooks,在看到 swr 之后,我开始有点觉得在数据流里用 hooks 可能是未来的趋势,因为大量的交互场景都可以通过 hook 沉淀,但也有一点点担心

  • 和框架强绑,脱离框架我们可能没有优势,但是有框架加持,就能做到比社区大量的 hooks 数据流都“好用”,因为中间复杂的事情可以交给框架处理,比如手写 Provider,比如自动处理 model 依赖等等

  • 约定式 model 定义,在 models 目录下建文件导出 hooks 就是一个 model

  • 单一 API,在组件层或者 model 层通过 useModel 来使用

总结

关于本文 作者:@云谦 原文:https://github.com/sorrycc/blog/issues/90

他曾分享过


【PPT】umi架构、生态和未来


为你推荐


【第1766期】蚂蚁金服@玉伯:我的前端成长之路


【第1598期】蚂蚁体验技术部的前世今生


【PPT】阿里@维奇:文档即是代码的研发新玩法



【活动】第十四届D2前端论坛,12月14号杭州见

    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存